package baidu_uid

import (
	"errors"
	"fmt"
	"sync"
	"time"
)

type UidGenerator interface {
	// GetUID  Get a unique ID
	//
	// @return UID
	// @throws UidGenerateException
	GetUID() (int64, error)

	// ParseUID Parse the UID into elements which are used to generate the UID. <br>
	//          Such as timestamp & workerId & sequence...
	//
	// @param uid uid
	// @return Parsed info
	//
	//
	ParseUID(uid int64) string
}

// DefaultUidGenerator 百度雪花算法
//
//		+----------+---------------------------------+--------------------------+-----------------+
//		|   sign   |     delta seconds               |   worker node  id        |  sequence       |
//		+----------+---------------------------------+--------------------------+-----------------+
//		  1bit          28 bit                            22 bit                    13 bit
//
//	 sign(1bit)
//			固定1bit符号标识，即生成的UID为正数。
//
//		delta seconds (28 bits)
//			当前时间，相对于时间基点"2016-05-20"的增量值，单位：秒，最多可支持约8.7年,建议调整为31bit用64年
//
//		worker id (22 bits)
//			机器id，最多可支持约420w次机器启动。内置实现为在启动时由数据库分配，默认分配策略为用后即弃，后续可提供复用策略。建议调整为19bit，支持5万多次重启
//
//		sequence (13 bits)
//			每秒下的并发序列，13 bits可支持每秒8192个并发。
type DefaultUidGenerator struct {
	lock *sync.Mutex

	bitsAllocator *BitsAllocator

	workerIdAssigner WorkerIdAssigner
	workerId         int64

	/**
	Volatile fields caused by nextId()
	*/
	lastSecond int64
	sequence   int64

	/**
	Customer epoch, unit as second. For example 2016-05-20 (ms: 1463673600000)
	*/
	epochStr     string
	epochSeconds int64
}

func NewDefaultUidGenerator(bitsAllocator *BitsAllocator, epochStr string, workerIdAssigner WorkerIdAssigner) (*DefaultUidGenerator, error) {

	du := &DefaultUidGenerator{
		lock:             &sync.Mutex{},
		bitsAllocator:    bitsAllocator,
		workerIdAssigner: workerIdAssigner,
		epochStr:         epochStr,
		sequence:         0,
		lastSecond:       -1,
	}

	// 返回的是当地时间
	epochTime, err := time.ParseInLocation(yyyy_MM_dd, epochStr, time.Local)
	if nil != err {
		return nil, err
	}

	// initialize worker id
	du.workerId = workerIdAssigner.AssignWorkerId()
	if du.workerId > bitsAllocator.getMaxWorkerId() {
		return nil, errors.New(fmt.Sprintf("Worker id %d exceeds the max %d", du.workerId, bitsAllocator.getMaxWorkerId()))
	}

	du.epochSeconds = int64(epochTime.Second())

	return du, nil
}

func (du *DefaultUidGenerator) GetUID() (int64, error) {
	return du.nextId()

}

func (du *DefaultUidGenerator) nextId() (int64, error) {
	/**
	如果时间有任何的回拨，那么直接抛出异常；
	如果当前时间和上一次是同一秒时间，那么sequence自增。如果同一秒内自增值超过2^13-1，那么就会自旋等待下一秒（getNextSecond）；
	如果是新的一秒，那么sequence重新从0开始；
	*/
	du.lock.Lock()
	defer du.lock.Unlock()

	currentSecond, err := du.getCurrentSecond()
	if nil != err {
		return -1, err
	}

	// Clock moved backwards, refuse to generate uid
	if currentSecond < du.lastSecond {
		refusedSeconds := du.lastSecond - currentSecond
		return -1, errors.New(fmt.Sprintf("Clock moved backwards. Refusing for %d seconds", refusedSeconds))
	}

	// At the same second, increase sequence
	if currentSecond == du.lastSecond {
		du.sequence = (du.sequence + 1) & du.bitsAllocator.getMaxSequence()
		// Exceed the max sequence, we wait the next second to generate uid
		if du.sequence == 0 {
			//自增值超过最大值，那么就会自旋等待下一秒
			currentSecond, err = du.getNextSecond(du.lastSecond)
			if nil != err {
				return -1, err
			}
		}

		// At the different second, sequence restart from zero
	} else {
		du.sequence = 0
	}

	du.lastSecond = currentSecond

	// Allocate bits for UID
	return du.bitsAllocator.allocate(currentSecond-du.epochSeconds, du.workerId, du.sequence), nil
}

func (du *DefaultUidGenerator) getNextSecond(lastTimestamp int64) (int64, error) {
	timestamp, err := du.getCurrentSecond()
	if nil != err {
		return -1, nil
	}
	//自旋等待下一秒
	for timestamp <= lastTimestamp {
		timestamp, err = du.getCurrentSecond()
		if nil != err {
			return -1, nil
		}
	}

	return timestamp, nil
}

func (du *DefaultUidGenerator) getCurrentSecond() (int64, error) {
	currentSecond := int64(time.Now().Second())
	if currentSecond-du.epochSeconds > du.bitsAllocator.getMaxDeltaSeconds() {
		return -1, errors.New(fmt.Sprintf("Timestamp bits is exhausted. Refusing UID generate. Now: %d", currentSecond))
	}

	return currentSecond, nil
}

func (du *DefaultUidGenerator) ParseUID(uid int64) string {

	totalBits := TOTAL_BITS
	signBits := SIGN_BITS
	timestampBits := du.bitsAllocator.getTimestampBits()
	workerIdBits := du.bitsAllocator.getWorkerIdBits()
	sequenceBits := du.bitsAllocator.getSequenceBits()

	// parse UID
	sequence := (uid << (totalBits - sequenceBits)) >> (totalBits - sequenceBits)
	workerId := (uid << (timestampBits + signBits)) >> (totalBits - workerIdBits)
	deltaSeconds := uid >> (workerIdBits + sequenceBits)

	thatTime := time.Unix(du.epochSeconds+deltaSeconds, 0)
	thatTimeStr := thatTime.Format(yyyy_MM_dd)

	// format as string
	return fmt.Sprintf("{\"UID\":\"%d\",\"timestamp\":\"%s\",\"workerId\":\"%d\",\"sequence\":\"%d\"}",
		uid, thatTimeStr, workerId, sequence)

}
